// 
//  Author:
//    Anton Lauridsen anton at lauridsen decimal-separator info
// 
//  Copyright (c) 2009, Anton Lauridsen
// 
//  All rights reserved.
// 
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
//       the documentation and/or other materials provided with the distribution.
//     * Neither the name of the [ORGANIZATION] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
// 
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
//  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 

using System;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using OpenMetaverse;

namespace OpenFuse.Backend
{
	public class Utilities
	{
		public static Primitive RezInventoryItem(SlSession session, OpenMetaverse.InventoryItem inventoryItem)
		{
			Primitive rezedPrimitive = null;

			// we need the LocalID of the rezzed object, this turns out to be a bit tricky, due to 
			// inconsistensies between OpenSim and Linden Labs grid.
			uint objectLocalID = uint.MinValue;
			
			// first register for an ObjectProperties event and an OnNewPrim event
			// by combining the outcome of these two events, we know when the rez as completed
			// and the LocalID of the rezzed object. Any object where we are the owner must
			// have been rezzed by us.
			Dictionary<UUID, uint> mergeObjectIDAndLocalID = new Dictionary<UUID, uint>();
			AutoResetEvent gotMyObject = new AutoResetEvent(false);
			EventHandler<ObjectPropertiesEventArgs> opc = delegate(object sender, ObjectPropertiesEventArgs e)
			{
				if(e.Properties.OwnerID == session.GridClient.Self.AgentID)
				{
					// if new object already registered this object, then it must be one
					// of the objects we've rezzed, as it contains the right owner id
					if(mergeObjectIDAndLocalID.ContainsKey(e.Properties.ObjectID))
					{
						objectLocalID = mergeObjectIDAndLocalID[e.Properties.ObjectID];
						gotMyObject.Set();
					} else {
						mergeObjectIDAndLocalID[e.Properties.ObjectID] = uint.MinValue;
					}
				}
			};
			session.GridClient.Objects.ObjectProperties += opc;

			EventHandler<PrimEventArgs> onNewPrim = delegate(object sender, PrimEventArgs e)
			{
				// if the new object is already registered, then it must have
				// come from the ObjectProperties event, and we now know both.
				if(mergeObjectIDAndLocalID.ContainsKey(e.Prim.ID))
				{
					objectLocalID = e.Prim.LocalID;
					gotMyObject.Set();
				} else {
					// workaround for a bug in OpenSim, where you cannot trust the owner id of the first
					// object properties package, by selecting it we force another object properties package
					// to be sent
					session.GridClient.Objects.SelectObject(session.GridClient.Network.CurrentSim, e.Prim.LocalID);
				}
				mergeObjectIDAndLocalID[e.Prim.ID] = e.Prim.LocalID;
			};

			session.GridClient.Objects.ObjectUpdate += onNewPrim;
			
			// next rez the object
			session.GridClient.Inventory.RequestRezFromInventory(session.GridClient.Network.CurrentSim, 
			                                                     new Quaternion(0.0f, 0.0f, 0.0f, 0.0f), 
			                                                     new Vector3( session.GridClient.Self.SimPosition.X,
			                                                                  session.GridClient.Self.SimPosition.Y,
			                                                                  session.GridClient.Self.SimPosition.Z+3.0f),
			                                                     inventoryItem,
			                                                     session.GridClient.Self.ActiveGroup,
			                                                     UUID.Random(),
			                                                     true);

			// finally wait until we know it has been rez'ed (or give up after 30 seconds)
			bool found = gotMyObject.WaitOne(120*1000);
			// stop listening for the events again
			session.GridClient.Objects.ObjectProperties -= opc;
			session.GridClient.Objects.ObjectUpdate -= onNewPrim;
			
			if(found)
			{
				List<Primitive> foundPrims = session.GridClient.Network.CurrentSim.ObjectsPrimitives.FindAll(delegate(Primitive prim) { return prim.LocalID == objectLocalID; });
				if(foundPrims.Count == 1) {
					uint rootLocalID = FindRootPrimitive(session, foundPrims[0]);
					rezedPrimitive = session.GridClient.Network.CurrentSim.ObjectsPrimitives.Find(p => p.LocalID == rootLocalID);

					// OpenSim sends a bogus intial ObjectProperties packet
					// so we force it to send a new one, due to network latency, this should do the trick
					if(rezedPrimitive.Properties == null)
						if(!RequestObjectProperties(session, rezedPrimitive))
							session.Logger.ErrorFormat("Failed to download properties for root object {0}", rezedPrimitive.ID);
				} else {
					session.Logger.ErrorFormat("Failed to find a rez'ed {0} object(s) found", foundPrims.Count);
				}
			}
			else
			{
				session.Logger.Error("Couldn't find the rezzed object");
			}
			return rezedPrimitive;
		}

		public static UUID DeRezObject(SlSession session, Primitive rezedPrimitive, DeRezDestination destinationType, UUID destinationFolder)
		{
			// make sure we are referencing the root object of the group			
			uint rootLocalId = FindRootPrimitive(session, rezedPrimitive);
			bool gotKillObject = false;
			bool gotObjectInventory = false;
			// wait for the kill object packet, otherwise we risk logging out before everything is in place
			AutoResetEvent objectKillWait = new AutoResetEvent(false);
			EventHandler<KillObjectEventArgs> onKillObject = delegate(object o, KillObjectEventArgs e)
			{
				if(e.ObjectLocalID == rootLocalId)
				{
					gotKillObject = true;
					objectKillWait.Set();
				}
			};
			
			UUID newInventoryItemID = UUID.Zero;
			AutoResetEvent objectInInvotoryWait = new AutoResetEvent(false);
			EventHandler<TaskItemReceivedEventArgs> HandleOnTaskItemReceived = 
				delegate(object o, TaskItemReceivedEventArgs e)
				{
					// TODO, be a little more selective in when to accept the result
					if(rezedPrimitive.Properties.CreatorID == e.CreatorID)
					{
						gotObjectInventory = true;
						newInventoryItemID = e.ItemID;
						objectInInvotoryWait.Set();
					}
				};
			
			EventHandler<FolderUpdatedEventArgs> HandleOnFolderUpdated =
				delegate(object o, FolderUpdatedEventArgs e) {
					session.Logger.Debug("-----OnFolderUpdated");	
				};
			
			EventHandler<ItemReceivedEventArgs> HandleOnItemReceived =
				delegate(object o, ItemReceivedEventArgs e) {
					session.Logger.Debug("-----OnItemReceived");	
				};

			session.GridClient.Inventory.FolderUpdated += HandleOnFolderUpdated;
			session.GridClient.Inventory.ItemReceived += HandleOnItemReceived;
			session.GridClient.Objects.KillObject += onKillObject;
			session.GridClient.Inventory.TaskItemReceived += HandleOnTaskItemReceived;

			session.GridClient.Inventory.RequestDeRezToInventory(rootLocalId, 
			                                                     destinationType,
			                                                     destinationFolder,
			                                                     UUID.Random()); 

			bool processCompleted = WaitHandle.WaitAll(new WaitHandle[]{ objectKillWait, objectInInvotoryWait}, 20*1000, true);
			session.GridClient.Objects.KillObject -= onKillObject;
			session.GridClient.Inventory.TaskItemReceived -= HandleOnTaskItemReceived;
			session.GridClient.Inventory.FolderUpdated -= HandleOnFolderUpdated;
			session.GridClient.Inventory.ItemReceived -= HandleOnItemReceived;
			if(!processCompleted) 
			{
				if(!gotObjectInventory)
					session.Logger.WarnFormat("Failed to de-rez '{0}', timeout while waiting for inventory storage confirmation", rezedPrimitive.Properties.Name);
				if(!gotKillObject)
					session.Logger.ErrorFormat("Failed to de-rez '{0}', object was not removed from scene", rezedPrimitive.Properties.Name);
			}

			return newInventoryItemID;
		}

		public static uint FindRootPrimitive(SlSession session, Primitive primitive)
		{
			uint rootLocalId = primitive.LocalID;
			if(rootLocalId  != 0)
			{
				Primitive p = primitive;
				while(p.ParentID != 0)
				{
					rootLocalId = p.ParentID;
					List<Primitive> parents = session.GridClient.Network.CurrentSim.ObjectsPrimitives.FindAll(prim => prim.LocalID == p.ParentID);

					// sometimes it takes a while for things to appear, lets check if we need to wait
					if(parents.Count == 0)
					{
						int maxWaitCount = 20;
						while(parents.Count == 0 && maxWaitCount > 0)
						{
							session.Logger.WarnFormat("Waiting for root item to appear {0}/20", 21-maxWaitCount);
							Thread.Sleep(1000);
							--maxWaitCount;
							parents = session.GridClient.Network.CurrentSim.ObjectsPrimitives.FindAll(prim => prim.LocalID == p.ParentID);
						}
						if(parents == null)
							return 0;
					}
					p = parents[0];
				}
			}
			return rootLocalId;
		}
	
		public static bool RequestObjectProperties(SlSession session, Primitive prim)
		{
			AutoResetEvent gotMyObject = new AutoResetEvent(false);
			
			EventHandler<ObjectPropertiesEventArgs> lookTouchResult = delegate(object o, ObjectPropertiesEventArgs e)
			{
				if(e.Properties.ObjectID == prim.ID)
					gotMyObject.Set();
			};
			
			session.GridClient.Objects.ObjectProperties += lookTouchResult;
			session.GridClient.Objects.SelectObject(session.GridClient.Network.CurrentSim, prim.LocalID);
			bool success = gotMyObject.WaitOne(5000);
			session.GridClient.Objects.ObjectProperties -= lookTouchResult;

			session.GridClient.Objects.DeselectObject(session.GridClient.Network.CurrentSim, prim.LocalID);
			return success;
		}

		public static bool RequestObjectProperties(SlSession session, List<Primitive> objects, int msPerRequest)
        {
			session.Logger.DebugFormat("RequestObjectProperties for {0} prims", objects.Count);
			
	        Dictionary<UUID, Primitive> primsWaiting = new Dictionary<UUID, Primitive>();
			AutoResetEvent allPropertiesReceived = new AutoResetEvent(false);
			
            // Create an array of the local IDs of all the prims we are requesting properties for
            uint[] localids = new uint[objects.Count];
            
			EventHandler<ObjectPropertiesEventArgs> gotObjectPropertiesEvent = delegate(object o, ObjectPropertiesEventArgs e)
        	{
            	lock(primsWaiting)
            	{
					primsWaiting[e.Properties.ObjectID].Properties = e.Properties;
                	primsWaiting.Remove(e.Properties.ObjectID);

                	if(primsWaiting.Count == 0)
                    	allPropertiesReceived.Set();
            	}
        	};

			
            lock(primsWaiting)
            {
                primsWaiting.Clear();

                for (int i = 0; i < objects.Count; ++i)
                {
                    localids[i] = objects[i].LocalID;
                    primsWaiting.Add(objects[i].ID, objects[i]);
                }
            }

			session.GridClient.Objects.ObjectProperties += gotObjectPropertiesEvent;
            session.GridClient.Objects.SelectObjects(session.GridClient.Network.CurrentSim, localids);
			allPropertiesReceived.WaitOne(2000 + msPerRequest * objects.Count);
			bool succeeded = primsWaiting.Count == 0;
			if(!succeeded)
			{
				Logger.Log("vvvvvvvvvvvvvvvvvvvvvvv", Helpers.LogLevel.Warning);
                foreach (UUID uuid in primsWaiting.Keys)
                	Logger.Log(uuid.ToString(), Helpers.LogLevel.Warning);
				Logger.Log("-----------------------", Helpers.LogLevel.Warning);
			}
			session.GridClient.Objects.ObjectProperties -= gotObjectPropertiesEvent;

            return succeeded;
        }

		#region Handle login
        private static string GetPlatform()
        {
            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Unix:
                    return "Linux";
                default:
                    return "Win";
            }
        }

        private static string GetMAC()
        {
            string mac = String.Empty;
            System.Net.NetworkInformation.NetworkInterface[] nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();

            if (nics.Length > 0)
                mac = nics[0].GetPhysicalAddress().ToString().ToUpper();

            if (mac.Length < 12)
                mac = mac.PadRight(12, '0');

            return String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
                mac.Substring(0, 2),
                mac.Substring(2, 2),
                mac.Substring(4, 2),
                mac.Substring(6, 2),
                mac.Substring(8, 2),
                mac.Substring(10, 2));
        }

		public static bool Login(SlSession session, string firstName, string lastName, string password, string loginUri)
		{
			AutoResetEvent loginCompleted = new AutoResetEvent(false);
			session.Logger.Debug("Starting login process");

#region delegates
			EventHandler<LoginProgressEventArgs> OnLoginProgress = delegate(object o, LoginProgressEventArgs e)
				{
					string status = null;
					switch(e.Status)
					{
						case LoginStatus.ConnectingToLogin:
							status = "ConnectingToLogin";
							break;
						case LoginStatus.ConnectingToSim:
							status = "ConnectingToSim";
							break;
						case LoginStatus.Failed:
							status = "Failed";
							loginCompleted.Set();
							break;
						case LoginStatus.None:
							status = "None";
							break;
						case LoginStatus.ReadingResponse:
							status = "ReadingResponse";
							break;
						case LoginStatus.Redirecting:
							status = "Redirecting";
							break;
						case LoginStatus.Success:
							loginCompleted.Set();
							break;
						default:
							throw new Exception("Unknown login status");
					}
					if(status != null)
						Console.WriteLine(string.Format("{0} <{1}>", e.Status, e.Message));
				};
			
#endregion
			
			List<string> options = new List<string>();

			LoginParams loginParams = new LoginParams();
			loginParams.URI = loginUri;
			loginParams.Timeout = 60*1000;
			loginParams.MethodName = "login_to_simulator";
			loginParams.FirstName = firstName;
			loginParams.LastName = lastName;
			loginParams.Password = password;
			loginParams.Start = "last";
			loginParams.Channel = "OpenFuse";
			loginParams.Version = "0.1";
			loginParams.Platform = GetPlatform();
			loginParams.MAC = GetMAC();
			loginParams.ViewerDigest = string.Empty;
			loginParams.Options = options.ToArray();
			// workaround for bots being caught up in a global ban
			// This *should* be the hash of the first hard drive, 
			// but any unique identifier works.
            loginParams.ID0 = GetMAC();
			
			session.GridClient.Network.LoginProgress += OnLoginProgress;
            session.GridClient.Network.BeginLogin(loginParams);

			loginCompleted.WaitOne();

			session.GridClient.Network.LoginProgress -= OnLoginProgress;

			if(!session.GridClient.Network.Connected)
				session.Logger.Error(session.GridClient.Network.LoginMessage);

			session.GridClient.Throttle.Set();
			session.Logger.DebugFormat("Login process completed status={0}", session.GridClient.Network.LoginStatusCode);
			return session.GridClient.Network.LoginStatusCode == LoginStatus.Success;
		}

		#endregion Handle login
	
		#region Handle logout
		public static void Logout(SlSession session)
		{
			session.Logger.Debug("Starting logout process");

			session.FlushQueue();
			
			if(session.GridClient.Network.Connected)
				session.GridClient.Network.Logout();
			
			session.Logger.Debug("Logout process completed");
		}
		#endregion Handle logout

		public static byte[] DownloadTexture(SlSession session, UUID textureId) 
		{
			return DownloadTexture(session, textureId, 0);
		}
		
		public static byte[] DownloadTexture(SlSession session, UUID textureId, int discardLevel)
		{
	        AutoResetEvent DownloadHandle = new AutoResetEvent(false);
            TextureRequestState requestState = TextureRequestState.Aborted;
	        OpenMetaverse.Assets.AssetTexture Asset = null;
			byte[] receivedTexture = null;
		
			if(ProtectedAssets.IsAssetProtected(textureId))
			{
				session.Logger.WarnFormat("Protected asset {0} is being ignored", textureId);
				return null;
			}
			
			EventHandler<ImageReceiveProgressEventArgs> ImageProgress = delegate(object o, ImageReceiveProgressEventArgs e)
				{
					// TODO implement progress indicator
//		            if (image == textureId)
//		                Console.WriteLine(String.Format("Texture {0}: Received {1} / {2} (Packet: {3})", image, recieved, total, lastPacket));
	        	};
				
	        OpenMetaverse.TextureDownloadCallback ImageReceived = delegate(TextureRequestState state, OpenMetaverse.Assets.AssetTexture asset)
		        {
					requestState = state;
	    	        Asset = asset;
	
		            DownloadHandle.Set();
	        	};

//			session.GridClient.Assets.ImageReceiveProgress += ImageProgress;
//            session.GridClient.Assets.ImageReceived += ImageReceived;
			
			session.GridClient.Assets.RequestImage(textureId, ImageType.Normal, 1000000.0f, 0, 0, ImageReceived, true);
			if(DownloadHandle.WaitOne(120 * 1000, false))
	        {
	        	if(requestState == TextureRequestState.Finished)
	            {
	            	if (Asset != null && Asset.Decode())
	                {
						receivedTexture = Asset.AssetData;
	                }
	                else
	                {
	                	session.Logger.ErrorFormat("Failed to decode texture {0}", textureId.ToString());
                    }
				}
	            else if (requestState == TextureRequestState.NotFound)
	            {
	            	session.Logger.ErrorFormat("Simulator reported texture not found: {0}", textureId.ToString());
	            }
	            else
	            {
	            	session.Logger.ErrorFormat("Download failed for texture {0}", textureId.ToString());
	            }
			}
	        else
	        {
	        	session.Logger.Error("Timed out waiting for texture download");
	        }

			session.GridClient.Assets.ImageReceiveProgress -= ImageProgress;

			return receivedTexture;
		}

		public static byte[] DownloadAsset(SlSession session, UUID assetID, AssetType assetType)
		{
			
			// we need to keep track of the time of the individual packets
			DateTime lastPacketReceiveTime = DateTime.Now;
//			UUID requestid = UUID.Zero;
			AutoResetEvent waitHandle = new AutoResetEvent(false);

			byte[] assetData = null;
			OpenMetaverse.Assets.Asset downloadedAsset = null;

//			if(assetId == UUID.Zero)
//			{
//				session.Logger.Warn("Requested asset ID is NULL, has the asset been removed from the asset store?");
//				return null;
//			}

			EventHandler<PacketReceivedEventArgs> transferPacketCallback = delegate(object o, PacketReceivedEventArgs e)
				{
					// Set a timestamp for when we received this packet
					lastPacketReceiveTime = DateTime.Now;
				};
	
			AssetManager.AssetReceivedCallback onAssetReceivedCallback = delegate(AssetDownload download, OpenMetaverse.Assets.Asset a)
				{
					if(download.Status == StatusCode.OK)
					{
						downloadedAsset = a;
					} else {
						session.Logger.DebugFormat("XXX download.Success={0}", download.Success.ToString());
						session.Logger.DebugFormat("XXX download.Transferred={0}", download.Transferred.ToString());
						session.Logger.DebugFormat("XXX download.Status={0}", download.Status.ToString());
						session.Logger.DebugFormat("XXX download.Transferred={0}", download.Transferred.ToString());
						session.Logger.DebugFormat("XXX download.AssetID={0}", assetID);
						session.Logger.Debug("This usually indicates a report of 'XXX is missing from database.");
					}
					waitHandle.Set();
				};

			// Keep track of the individual packets received over the network
			session.GridClient.Network.RegisterCallback(OpenMetaverse.Packets.PacketType.TransferPacket, transferPacketCallback);

			session.GridClient.Assets.RequestAsset(assetID, assetType, true, onAssetReceivedCallback);
			lastPacketReceiveTime = DateTime.Now;

			waitHandle.Reset();

			// Wait until the asset is received or we've stopped receiving packets
			// LibOpenMetaverse cannot be relied upon to inform if reception of an asset failed
			bool completed = false;
			while((DateTime.Now - lastPacketReceiveTime).TotalMilliseconds < 10000 && !completed)
			{
				completed = waitHandle.WaitOne(1000, false);
			}
			
			if(completed && downloadedAsset != null)
			{
				waitHandle = null;

				assetData = downloadedAsset.AssetData;
			}
			else
			{
				// before giving up we should retry
				session.Logger.Warn("timeout waiting for " + assetID + ":" + (int)assetType + " " + (DateTime.Now - lastPacketReceiveTime).TotalMilliseconds.ToString());
			}

           	session.GridClient.Network.UnregisterCallback(OpenMetaverse.Packets.PacketType.TransferPacket, transferPacketCallback);

			return assetData;
		}
		
		public static byte[] DownloadAsset(SlSession session, OpenMetaverse.InventoryItem item)
		{
			byte[] data = null;
			int retryCount = 0;
			
			while(data == null && retryCount < 1)
			{
				data = DownloadAsset(session, item.AssetUUID, item.AssetType);
				retryCount++;
			}
			
			if(data == null)
				session.Logger.Warn("giving up downloading:" + item.AssetUUID);
			
			return data;
		}
		
		public static TaskInventory DownloadTaskInventory(SlSession session, Primitive p)
		{
			TaskInventory result = null;
			
			List<InventoryBase> items = session.GridClient.Inventory.GetTaskInventory(p.ID, p.LocalID, 1000 * 60);
            if (items != null)
            {
				result = new TaskInventory();
				
				session.Logger.DebugFormat("---- inventory contents of prim {0}", p.Properties.Name);
                for(int i = 0; i < items.Count; i++)
                    if(items[i] is InventoryItem) 
					{
						result.Add((OpenMetaverse.InventoryItem)items[i]);
						session.Logger.DebugFormat("---- {0}", items[i].Name);
					}
				result.Id = GetTaskInventoryID(session, p.ID, p.LocalID);
            }
            else
            {
				session.Logger.Error("Failed to download task inventory for " + p.ID.Guid);
            }
			return result;
		}
		
		public static UUID GetTaskInventoryID(SlSession session, UUID objectID, uint objectLocalID)
		{
			UUID taskInventoryID = UUID.Zero;
			
			AutoResetEvent taskReplyEvent = new AutoResetEvent(false);
			
		 	EventHandler<TaskInventoryReplyEventArgs> onTaskInventoryReply =
                delegate(object o, TaskInventoryReplyEventArgs e)
                {
                    if (e.ItemID == objectID)
                    {
						taskInventoryID = e.ItemID;
                        taskReplyEvent.Set();
                    }
                };

			session.GridClient.Inventory.TaskInventoryReply += onTaskInventoryReply;
            session.GridClient.Inventory.RequestTaskInventory(objectLocalID);

            if(!taskReplyEvent.WaitOne(4000, false))
				session.Logger.ErrorFormat("failed to get task id for: {0}", objectLocalID);
			
			session.GridClient.Inventory.TaskInventoryReply -= onTaskInventoryReply;
			
			return taskInventoryID;
		}

		public static UUID GetTaskInventoryFolderID(SlSession session, Primitive p)
		{
			List<InventoryBase> items = session.GridClient.Inventory.GetTaskInventory(p.ID, p.LocalID, 1000 * 60);
            if (items != null)
            {
                for(int i = 0; i < items.Count; i++)
                    if(items[i] is InventoryFolder)
						return items[i].UUID;
            }
            else
            {
				session.Logger.Error("Failed to download task inventory for " + p.ID.Guid);
            }
			return UUID.Zero;
		}

		public static UUID CreateFolder(SlSession session, UUID parentFolderID, string folderName)
		{
			UUID createdFolderID = UUID.Zero;

			// if the folder already exists, there is little point in creating it
			List<InventoryBase> content = session.GridClient.Inventory.FolderContents(parentFolderID, session.AvatarId, true, false, InventorySortOrder.ByName, 40000);
			if(content != null)
			{
				foreach(InventoryBase ib in content)
				{
					if(ib.Name == folderName && ib is InventoryFolder)
						createdFolderID = ib.UUID;
				}
			} else {
				session.Logger.Warn("Unable to retrieve contents for parent folder");
			}
			
			if(createdFolderID == UUID.Zero)
			{
				createdFolderID = session.GridClient.Inventory.CreateFolder(parentFolderID, folderName);
			}
			return createdFolderID;
		}

//		public static byte[] DownloadAssetFromInventory(SlSession session, UUID itemID, UUID assetID, AssetType assetType)
//		{
//			return DownloadAssetFromInventory(session, UUID.Zero, itemID, assetID, assetType);
//		}
		
		public static byte[] DownloadAssetFromInventory(SlSession session, UUID taskID, UUID itemID, UUID assetID, AssetType assetType)
		{
			// we need to keep track of the time of the individual packets
			DateTime lastPacketReceiveTime = DateTime.Now;
			ManualResetEvent waitHandle = new ManualResetEvent(false);

			byte[] assetData = null;
			OpenMetaverse.Assets.Asset downloadedAsset = null;

			EventHandler<PacketReceivedEventArgs> transferPacketCallback = delegate(object o, PacketReceivedEventArgs e)
				{
					// Set a timestamp for when we received this packet
					lastPacketReceiveTime = DateTime.Now;
				};
	
			AssetManager.AssetReceivedCallback onAssetReceivedCallback = delegate(AssetDownload download, OpenMetaverse.Assets.Asset a)
				{
					if(download.Status == StatusCode.OK)
					{
						downloadedAsset = a;
						if(downloadedAsset.AssetID == UUID.Zero)
						{
							session.Logger.Debug("XXX Empty asset ID");
//								downloadedAsset.AssetID = assetID;
						}
					} else {
						session.Logger.DebugFormat("XXX download.Success={0}", download.Success.ToString());
						session.Logger.DebugFormat("XXX download.Transferred={0}", download.Transferred.ToString());
						session.Logger.DebugFormat("XXX download.Status={0}", download.Status.ToString());
						session.Logger.DebugFormat("XXX download.Transferred={0}", download.Transferred.ToString());
						session.Logger.DebugFormat("XXX download.AssetID={0}", assetID);
					}
					waitHandle.Set();
				};

			// Keep track of the individual packets received over the network
			session.GridClient.Network.RegisterCallback(OpenMetaverse.Packets.PacketType.TransferPacket, transferPacketCallback);

			session.GridClient.Assets.RequestInventoryAsset(assetID, itemID, taskID, session.AvatarId, assetType, true, onAssetReceivedCallback);
			lastPacketReceiveTime = DateTime.Now;

			waitHandle.Reset();

			// Wait until the asset is received or we've stopped receiving packets
			// LibOpenMetaverse cannot be relied upon to inform if reception of an asset failed
			bool completed = false;
			while((DateTime.Now - lastPacketReceiveTime).TotalMilliseconds < 20000 && !completed)
			{
				completed = waitHandle.WaitOne(1000, false);
			}
			
			if(completed && downloadedAsset != null)
			{
				waitHandle = null;

				assetData = downloadedAsset.AssetData;
			}
			else
			{
				session.Logger.Error("timeout waiting for inventory asset " + assetID + " " + (DateTime.Now - lastPacketReceiveTime).TotalMilliseconds.ToString());
			}

           	session.GridClient.Network.UnregisterCallback(OpenMetaverse.Packets.PacketType.TransferPacket, transferPacketCallback);

			return assetData;
		}

		/// <summary>
		/// Move an item from a task folder to an inventory folder
		/// </summary>
		/// <returns>
		/// A the UUID of the item in the folder <see cref="UUID"/>
		/// </returns>
		public static UUID MoveItemFromTaskInventoryToAgentFolder(SlSession session, uint localID, UUID srcItemID, UUID dstFolderID)
		{
			AutoResetEvent gotIventoryItem = new AutoResetEvent(false);
			UUID newInventoryItemID = UUID.Zero;
			EventHandler<TaskItemReceivedEventArgs> HandleOnTaskItemReceived = 
				delegate(object o, TaskItemReceivedEventArgs e)
				{
					if(e.FolderID == dstFolderID)
					{
						gotIventoryItem.Set();
						newInventoryItemID = e.ItemID;
					}
				};

			
			session.GridClient.Inventory.TaskItemReceived += HandleOnTaskItemReceived;
			session.GridClient.Inventory.MoveTaskInventory(localID, srcItemID, dstFolderID, session.GridClient.Network.CurrentSim);
			gotIventoryItem.WaitOne(30000);
			session.GridClient.Inventory.TaskItemReceived -= HandleOnTaskItemReceived;
			
			return newInventoryItemID;
		}

		public static void HardDeleteItem(SlSession session, UUID itemID)
		{
			OpenMetaverse.Packets.RemoveInventoryItemPacket packet = new OpenMetaverse.Packets.RemoveInventoryItemPacket();
			packet.AgentData.AgentID = session.AvatarId;
			packet.AgentData.SessionID = session.GridClient.Self.SessionID;
			packet.InventoryData = new OpenMetaverse.Packets.RemoveInventoryItemPacket.InventoryDataBlock[1];
			packet.InventoryData[0] = new OpenMetaverse.Packets.RemoveInventoryItemPacket.InventoryDataBlock();
			packet.InventoryData[0].ItemID = itemID;
			session.GridClient.Network.SendPacket(packet);
		}

		public static byte[] DownloadNoTransferAnimation(SlSession session, UUID assetID)
		{
			session.Logger.DebugFormat("Download protected animation {0}", assetID);

            OpenMetaverse.Packets.AgentAnimationPacket animate = new OpenMetaverse.Packets.AgentAnimationPacket();
            animate.Header.Reliable = true;

            animate.AgentData.AgentID = session.GridClient.Self.AgentID;
            animate.AgentData.SessionID = session.GridClient.Self.SessionID;
            animate.AnimationList = new OpenMetaverse.Packets.AgentAnimationPacket.AnimationListBlock[1];

            animate.AnimationList[0] = new OpenMetaverse.Packets.AgentAnimationPacket.AnimationListBlock();
            animate.AnimationList[0].AnimID = assetID;
            animate.AnimationList[0].StartAnim = false;



			EventHandler<AvatarAnimationEventArgs> handleOnAvatarAnimation = 
				delegate(object o, AvatarAnimationEventArgs e) {
				};

			// Keep track of the individual packets received over the network
			session.GridClient.Avatars.AvatarAnimation += handleOnAvatarAnimation;
				
			session.GridClient.Network.SendPacket(animate);
			byte[] data = DownloadAsset(session, assetID, AssetType.Animation);
			if(data == null)
				session.Logger.DebugFormat("Failed in downloading protected asset {0}", assetID);
			session.GridClient.Avatars.AvatarAnimation -= handleOnAvatarAnimation;
			return data;
		}
	}
}

